// This file is part of Notepad4.
// See License.txt for details about distribution and modification.
//! Lexer for LLVM, MLIR.

#include <cassert>
#include <cstring>

#include <string>
#include <string_view>

#include "ILexer.h"
#include "Scintilla.h"
#include "SciLexer.h"

#include "WordList.h"
#include "LexAccessor.h"
#include "Accessor.h"
#include "StyleContext.h"
#include "CharacterSet.h"
#include "StringUtils.h"
#include "LexerModule.h"

using namespace Lexilla;

namespace {

struct EscapeSequence {
	int outerState = SCE_LLVM_DEFAULT;
	int digitsLeft = 0;

	// highlight any character as escape sequence.
	bool resetEscapeState(int state, int chNext, bool quoted) noexcept {
		outerState = state;
		if (IsHexDigit(chNext)) {
			digitsLeft = 2;
			return true;
		}
		digitsLeft = IsEOLChar(chNext)? 0 : 1;
		return quoted;
	}
	bool atEscapeEnd(int ch) noexcept {
		--digitsLeft;
		return digitsLeft <= 0 || !IsHexDigit(ch);
	}
};

//KeywordIndex++Autogenerated -- start of section automatically generated
enum {
	KeywordIndex_Keyword = 0,
	KeywordIndex_Type = 1,
	KeywordIndex_Attribute = 2,
	KeywordIndex_Instruction = 3,
	MaxKeywordSize = 36,
};
//KeywordIndex--Autogenerated -- end of section automatically generated

enum class KeywordType {
	None = SCE_LLVM_DEFAULT,
	Label = SCE_LLVM_LABEL,
};

constexpr bool IsLLVMIdentifierChar(int ch) {
	return IsIdentifierChar(ch) || ch == '-' || ch == '.' || ch == '$';
}

int CheckLLVMVarType(StyleContext &sc, KeywordType kwType) noexcept {
	int state = sc.state;
	if (kwType == KeywordType::Label || sc.ch == ':') {
		state = SCE_LLVM_LABEL;
	} else {
		Sci_PositionU endPos = sc.currentPos;
		Sci_PositionU pos = endPos;
		int chNext = sc.GetLineNextCharEx(pos);
		if (chNext == '(') {
			pos = sc.styler.GetStartSegment();
		} else if (chNext == '=') {
			while (pos < sc.lineStartNext) {
				++pos;
				chNext = static_cast<uint8_t>(sc.styler[pos]);
				if (!isspacechar(chNext)) {
					break;
				}
			}
			if (chNext != 't') {
				return state;
			}
			endPos = sc.lineStartNext;
		} else {
			return state;
		}

		char s[8];
		sc.styler.GetRange(pos, endPos, s, sizeof(s));
		if (chNext == '(') {
			const bool quoted = sc.chPrev == '\"';
			if (StrStartsWith(s + (quoted? 2 : 1), "llvm.")) {
				state = SCE_LLVM_INTRINSIC;
			} else {
				state = SCE_LLVM_FUNCTION;
			}
		} else {
			if (StrStartsWith(s, "type")) {
				const uint8_t ch = s[4];
				if (ch <= ' ' || ch == '{' || ch == '<') {
					// var = type {}, type <>
					state = SCE_LLVM_TYPE;
				}
			}
		}
	}
	return state;
}

void ColouriseLLVMDoc(Sci_PositionU startPos, Sci_Position lengthDoc, int initStyle, LexerWordList keywordLists, Accessor &styler) {
	int lineStateLineComment = 0;
	KeywordType kwType = KeywordType::None;
	int visibleChars = 0;
	bool identifierQuoted = false;
	EscapeSequence escSeq;

	StyleContext sc(startPos, lengthDoc, initStyle, styler);
	if (sc.currentLine > 0) {
		const int lineState = styler.GetLineState(sc.currentLine - 1);
		identifierQuoted = lineState & 2;
	}

	while (sc.More()) {
		switch (sc.state) {
		case SCE_LLVM_OPERATOR:
			sc.SetState(SCE_LLVM_DEFAULT);
			break;

		case SCE_LLVM_NUMBER:
			if (!IsDecimalNumber(sc.chPrev, sc.ch, sc.chNext)) {
				sc.SetState(SCE_LLVM_DEFAULT);
			}
			break;

		case SCE_LLVM_STRING:
		case SCE_LLVM_IDENTIFIER:
		case SCE_LLVM_COMDAT:
		case SCE_LLVM_METADATA:
		case SCE_LLVM_ATTRIBUTE_GROUP:
		case SCE_LLVM_VARIABLE:
		case SCE_LLVM_GLOBAL_VARIABLE:
			if (sc.ch == '\\' && escSeq.resetEscapeState(sc.state, sc.chNext, identifierQuoted)) {
				if (escSeq.digitsLeft) {
					sc.SetState(SCE_LLVM_ESCAPECHAR);
					sc.Forward();
				}
			} else if (identifierQuoted ? sc.ch == '\"' : !IsLLVMIdentifierChar(sc.ch)) {
				if (identifierQuoted) {
					identifierQuoted = false;
					sc.Forward();
				}

				int state = sc.state;
				const KeywordType kwPrev = kwType;
				kwType = KeywordType::None;
				if (sc.state >= SCE_LLVM_VARIABLE) {
					state = CheckLLVMVarType(sc, kwPrev);
				} else if (sc.state == SCE_LLVM_IDENTIFIER) {
					char s[MaxKeywordSize];
					sc.GetCurrent(s, sizeof(s));
					if (keywordLists[KeywordIndex_Keyword].InList(s)) {
						state = SCE_LLVM_WORD;
					} else if (keywordLists[KeywordIndex_Type].InList(s)) {
						state = SCE_LLVM_WORD2;
						if (StrEqual(s, "label")) {
							kwType = KeywordType::Label;
						}
					} else if (keywordLists[KeywordIndex_Attribute].InListPrefixed(s, '(')) {
						state = SCE_LLVM_ATTRIBUTE;
					} else if (keywordLists[KeywordIndex_Instruction].InList(s)) {
						state = SCE_LLVM_INSTRUCTION;
					} else if (sc.ch == ':' && visibleChars == sc.LengthCurrent()) {
						state = SCE_LLVM_LABEL;
					}
				}
				sc.ChangeState(state);
				sc.SetState(SCE_LLVM_DEFAULT);
			}
			break;

		case SCE_LLVM_WORD2:
			if (!IsADigit(sc.ch)) {
				if (IsLLVMIdentifierChar(sc.ch)) {
					sc.ChangeState(SCE_LLVM_IDENTIFIER);
				} else {
					sc.SetState(SCE_LLVM_DEFAULT);
				}
			}
			break;

		case SCE_LLVM_ESCAPECHAR:
			if (escSeq.atEscapeEnd(sc.ch)) {
				sc.SetState(escSeq.outerState);
				continue;
			}
			break;

		case SCE_LLVM_COMMENTLINE:
			if (sc.atLineStart) {
				sc.SetState(SCE_LLVM_DEFAULT);
			}
			break;

		case SCE_LLVM_COMMENTBLOCK:
			if (sc.Match('*', '/')) {
				sc.Forward();
				sc.ForwardSetState(SCE_LLVM_DEFAULT);
			}
			break;
		}

		if (sc.state == SCE_LLVM_DEFAULT) {
			if (sc.ch == ';' || sc.Match('/', '/')) {
				sc.SetState(SCE_LLVM_COMMENTLINE);
				if (visibleChars == 0) {
					lineStateLineComment = SimpleLineStateMaskLineComment;
				}
			} else if (sc.Match('/', '*')) {
				sc.SetState(SCE_LLVM_COMMENTBLOCK);
				sc.Forward();
			} else if (sc.ch == '\"' || sc.Match('c', '\"')) {
				identifierQuoted = true;
				sc.SetState(SCE_LLVM_STRING);
				if (sc.ch == 'c') {
					sc.Forward();
				}
			} else if (IsNumberStart(sc.ch, sc.chNext)) {
				sc.SetState(SCE_LLVM_NUMBER);
			} else if (IsIdentifierStart(sc.ch)) {
				identifierQuoted = false;
				sc.SetState(SCE_LLVM_IDENTIFIER);
				if (sc.ch == 'x' && sc.chNext <= ' ' && sc.chPrev <= ' ') {
					sc.ChangeState(SCE_LLVM_OPERATOR);
				} else if (AnyOf<'s', 'u'>(sc.ch) && (sc.chNext == '0' || sc.chNext == 'i')) {
					++visibleChars;
					sc.Forward();
					if (sc.Match('0', 'x')) {
						sc.ChangeState(SCE_LLVM_NUMBER); // LLVM: [s|u]0x
					}
				}
				if (sc.state == SCE_LLVM_IDENTIFIER && sc.ch == 'i' && IsADigit(sc.chNext)) {
					sc.ChangeState(SCE_LLVM_WORD2); // i<N>, MLIR: [s|u]i<N>
				}
			} else if (IsAGraphic(sc.ch)) {
				sc.SetState(SCE_LLVM_OPERATOR);
				if (AnyOf(sc.ch, '@', '%', '!', '$', '#', '^') && (sc.chNext == '\"' || IsLLVMIdentifierChar(sc.chNext))) {
					identifierQuoted = sc.chNext == '\"';
					constexpr unsigned mask = (SCE_LLVM_GLOBAL_VARIABLE << (4*('@' & 7)))
						| (SCE_LLVM_VARIABLE << (4*('%' & 7)))
						| (SCE_LLVM_METADATA << (4*('!' & 7)))
						| (SCE_LLVM_COMDAT << (4*('$' & 7)))
						| (SCE_LLVM_ATTRIBUTE_GROUP << (4*('#' & 7)))
						| (SCE_LLVM_VARIABLE << (4*('^' & 7))); // LLVM summary id, MLIR caret id, label
					const int state = (mask >> (4*(sc.ch & 7))) & 15;
					sc.ChangeState(state);
					sc.Forward();
				}
			}
		}

		if (!isspacechar(sc.ch)) {
			++visibleChars;
		}
		if (sc.atLineEnd) {
			lineStateLineComment += static_cast<int>(identifierQuoted)*2;
			styler.SetLineState(sc.currentLine, lineStateLineComment);
			lineStateLineComment = 0;
			visibleChars = 0;
		}
		sc.Forward();
	}

	sc.Complete();
}

static_assert(SCE_LLVM_OPERATOR == SCE_SIMPLE_OPERATOR);
}

extern const LexerModule lmLLVM(SCLEX_LLVM, ColouriseLLVMDoc, "llvm", FoldSimpleDoc);
